﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace PengSW.UdpHelper
{
    public class UdpListener
    {
        public UdpListener(int aPort = 15552)
        {
            ListenPort = aPort;
        }

        public event Action<object, string> Received;
        public event Action<object, byte[]> BytesReceived;
        public event Action<object, string> Error;

        public int ListenPort { get => _ListenPort; set { if (value < 1000) _ListenPort = 1000; else if (value > IPEndPoint.MaxPort) _ListenPort = IPEndPoint.MaxPort; else _ListenPort = value; } }
        private int _ListenPort = 15552;

        public Encoding ReceiveEncoding { get; set; } = Encoding.UTF8;

        private UdpClient _Listener = null;
        private Thread _ListenThread = null;

        public void StartListen(int aPort)
        {
            ListenPort = aPort;
            _ListenThread = new Thread(new ThreadStart(ListenThread))
            {
                IsBackground = true,
            };
            _Listener = new UdpClient(ListenPort);
            _ListenThread.Start();
        }
        public void StartListen() => StartListen(ListenPort);

        public void StopListen()
        {
            _ListenThread?.Abort();
            _ListenThread = null;
            _Listener?.Close();
            _Listener = null;
        }

        public bool CanStartListen => _Listener == null && ListenPort >= 1000;
        public bool CanStopListen => _Listener != null;

        public bool IsListening => CanStopListen;

        public static int GetFreePort(int aStartPort = 15555)
        {
            IPGlobalProperties aIpProperties = IPGlobalProperties.GetIPGlobalProperties();
            List<IPEndPoint> aEndPoints = new List<IPEndPoint>(aIpProperties.GetActiveTcpListeners());
            aEndPoints.AddRange(aIpProperties.GetActiveUdpListeners());
            for (int aPort = aStartPort; aPort < IPEndPoint.MaxPort; aPort++)
            {
                if (aEndPoints.Exists(r => r.Port == aPort)) continue;
                return aPort;
            }
            return 0;
        }

        public static void Send(string aHost, int aPort, string aMessage, Encoding aEncoding)
        {
            using (UdpClient aUdpClient = new UdpClient(aHost, aPort))
            {
                byte[] aBytes = aEncoding.GetBytes(aMessage);
                aUdpClient.Send(aBytes, aBytes.Length);
            }
        }

        private void ListenThread()
        {
            try
            {
                while (true)
                {
                    if (_Listener.Available > 0)
                    {
                        var aEndpoint = new IPEndPoint(IPAddress.Any, 0);
                        var aBytes = _Listener.Receive(ref aEndpoint);
                        if (ReceiveEncoding != null)
                        {
                            var aMessage = ReceiveEncoding.GetString(aBytes, 0, aBytes.Length);
                            Received?.Invoke(this, aMessage);
                        }
                        BytesReceived?.Invoke(this, aBytes);
                    }
                    Thread.Sleep(10);
                }
            }
            catch (ThreadAbortException)
            {
                Thread.ResetAbort();
            }
            catch (Exception ex)
            {
                Error?.Invoke(this, ex.Message);
            }
        }
    }
}
